feat(metrics): current-in-Qdrant chunk-density snapshot histogram - #1065
Conversation
The Tenant Fleet dashboard panel "Chunk density distribution (chunks/MB)"
renders increase(astrolabe_document_chunk_density_chunks_per_mb_bucket[...]),
an ingest-time flow histogram observed once per document at parse. It does not
represent the density distribution of the documents currently resident in
Qdrant (monotonic, reset-on-restart, window-scoped).
Add a current-state snapshot metric recomputed periodically from live Qdrant
contents:
- Persist source_bytes (ingested_byte_size) in the Qdrant point payload
(payload_keys.SOURCE_BYTES) — the density denominator, previously computed at
ingest and then discarded. Numerator (total_chunks) is already on every point.
- New GaugeHistogram astrolabe_qdrant_chunk_density_chunks_per_mb_current
(custom collector, doc_type label, buckets shared with the ingest histogram
via CHUNK_DENSITY_BUCKETS) — the correct Prometheus type for a snapshot
distribution that rises and falls.
- vector_density_snapshot_task scrolls chunk_index=0 non-placeholder points on
its own slower cadence, computes per-doc_type density, and publishes the
snapshot. Scan cap surfaced via astrolabe_qdrant_chunk_density_snapshot_truncated
(no silent cap).
- Forward-only coverage: only documents (re)ingested after this ships carry
source_bytes. Docs without a usable size are reported via
astrolabe_qdrant_chunk_density_uncovered_documents{doc_type} so partial
coverage is explicit rather than silently shrinking the histogram.
- Config knobs VECTOR_DENSITY_SNAPSHOT_{ENABLED,INTERVAL,MAX_DOCUMENTS}; task
spawned in both app lifespan branches, gated on the enable flag.
Tests: unit (bucketing, cumulative/gcount/gsum exposition, snapshot replace,
uncovered/truncated gauges, compute pagination/truncation/failure-swallow) and
integration (compute over a real in-memory Qdrant engine: covered vs uncovered
vs excluded-placeholder, truncation signalling).
Deck #638 (board 9). Cross-repo payload contract with the astrolabe-cloud-website
external processor to be mirrored as a follow-up (board 11).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @cbcoutinho's task in 3m 2s —— View job Claude PR Review
|
Round-1 review + SonarCloud follow-ups on the chunk-density snapshot. - compute_chunk_density_snapshot: check Qdrant's authoritative end-of-scroll (offset is None) FIRST and only flag truncated when scanned strictly exceeds the cap with more pages pending. Qdrant returns a non-None next offset even at the exact end, so the previous `scanned >= cap and offset is not None` could fire the alertable ..._snapshot_truncated gauge on a collection sized exactly at the cap. Tolerates one page of slop; adds an exact-boundary regression test and reshapes the truncation test to the corrected semantics. - Tests: use pytest.approx for the metric-count assertions (SonarCloud flagged exact float-equality as a reliability bug) and drop an async-def-without-await in the snapshot-task test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Round-1 findings addressed in
Also cleared the SonarCloud Quality Gate failure (C Reliability on new code):
32 unit + integration tests pass; ruff + ty green locally. |
|




Why
The Astrolabe Cloud — Tenant Fleet dashboard panel "Chunk density distribution (chunks/MB)" renders
increase(astrolabe_document_chunk_density_chunks_per_mb_bucket[...])— an ingest-time flow histogram observed once per document at parse (record_chunk_density). It answers "what was the density of documents as they streamed through ingestion", not "what is the density distribution of the documents currently in Qdrant" (the ingest histogram is monotonic, resets on pod restart, and is window-scoped; deletions/re-ingests are never removed).This adds a current-state snapshot metric recomputed periodically from live Qdrant contents, so the dashboard can render a faithful current distribution panel next to the ingest-flow one.
Why new data was required
density =
chunks / (source_bytes / 1e6). The numerator (total_chunks) is already on every point, but the denominatorsource_byteswas computed at ingest and then discarded — onlyfile_size(files-only) was persisted, and for text doc types the source size is unrecoverable from Qdrant (only chunk excerpts are stored). Sosource_bytesmust be persisted going forward.What
source_bytesin the Qdrant point payload (payload_keys.SOURCE_BYTES), written inprocessor._index_document.astrolabe_qdrant_chunk_density_chunks_per_mb_currentvia a custom collector (first in the repo) — the semantically-correct type for a snapshot distribution that rises and falls.doc_typelabel; buckets shared with the ingest histogram (CHUNK_DENSITY_BUCKETS) so the two panels are directly comparable.vector_density_snapshot_task(metrics_publisher.py) scrollschunk_index=0non-placeholder points on its own slower cadence, computes per-doc_typedensity, and publishes the snapshot. Scan cap surfaced viaastrolabe_qdrant_chunk_density_snapshot_truncated(no silent cap).source_bytes; unchanged docs are dedup-skipped on re-scan and stay uncovered. Docs without a usable size are reported viaastrolabe_qdrant_chunk_density_uncovered_documents{doc_type}so the coverage gap is explicit rather than silently shrinking the histogram.VECTOR_DENSITY_SNAPSHOT_{ENABLED,INTERVAL(300s),MAX_DOCUMENTS(50000)}; task spawned in both app lifespan branches, gated on the enable flag.Testing
tests/unit/test_chunk_density_snapshot_metric.py(bucketing, cumulativele/gcount/gsumexposition, snapshot replace, uncovered/truncated gauges) andtests/unit/vector/test_metrics_publisher.py(compute pagination, truncation, failure-swallow, scroll filter shape, task loop).tests/integration/test_chunk_density_snapshot.py:compute_chunk_density_snapshotover a real in-memory Qdrant engine (covered vs uncovered vs excluded-placeholder; truncation signalling).publish_chunk_density_snapshot()against a seeded in-memory Qdrant and scraping the registry: 90 chunks/MB →le=91, 8 chunks/MB →le=10, uncovered file counted separately.Test coverage follow-ups (per repo CLAUDE.md test gate)
astrolabe-cloud-websiteexternal document-processor (seepayload_keys.pydocstring). The processor should write the samesource_byteskey — tracked as a follow-up, not in this PR.sum by (le) (astrolabe_qdrant_chunk_density_chunks_per_mb_current_bucket{...})+ an uncovered-docs stat — separate change.Deck #638 (board 9 — Platform Observability). Shared
source_bytesdependency with Deck #636 (density baseline).This PR was generated with the help of AI, and reviewed by a Human